﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;

namespace Mimoza.Transport
{
    /// <summary>
	/// Represents connection between hosts.	
	/// </summary>
    public class Connection : IDisposable   
    {
        /// <summary>
        /// Delegate for processing connection close
        /// </summary>
        public delegate void ConnectionCloseHandler(Connection conn);

        public Connection(Socket socket)
        {
            FillSocket(socket);
        }

        public Connection(string remoteIpOrHostName, int port)
        {
            IPEndPoint remoteEndPoint = EndPoint.CreateEndPoint(remoteIpOrHostName, port);
            CreateConnection(remoteEndPoint);
        }

        #region IDisposable Members

        public void Dispose()
        {
            Close();
        }

        #endregion

        public ConnectionCloseHandler CloseHandler
        {
            get { return m_closeHandler; }
            set { m_closeHandler = value; }
        }

        virtual public string RemoteAddress
        {
            get
            {
                return m_remoteLocation;
            }
        }

        virtual public int RemotePort
        {
            get
            {
                return m_remotePort;
            }
        }

        public string LocalAddress
        {
            get
            {
                if (m_socket != null)
                {
                    IPEndPoint ipLocal = m_socket.LocalEndPoint as IPEndPoint;
                    if (ipLocal != null) return ipLocal.Address.ToString();
                }
                return "";
            }
        }

        public bool HasUnprocessedData
        {
            get
            {
                return SocketStream.HasUnprocessedData;
            }
        }

        public void MakeRequest(TransferObject request, out TransferObject reply)
        {
            Packet replyPacket = null;
            reply = null;

            Packet requestPacket = new Packet(request);
            
            try
            {
                Send(requestPacket, null);
                replyPacket = Recieve();
            }
            catch (System.Exception e)
            {
                Common.Logger.Log.Warn("Connection.MakeRequest error - '" + e.ToString() + "'.");
                Close();
                throw new ConnectionBroken(RemoteAddress, e);
            }

            if (replyPacket != null)
            {
                TransferObject userObjectStub = Transport.Dispatcher.GetStubTransferObject(replyPacket.UserDataTypeName);
                reply = replyPacket.RestoreUserData(userObjectStub);
                if (reply is ErrorReply)
                {
                    (reply as ErrorReply).ThowIt();
                }
            }
        }

        public TransferObject MakeRequest(TransferObject request)
        {
            TransferObject reply = null;
            MakeRequest(request, out reply);
            return reply;
        }

        public void SendRequest(TransferObject request)
        {
            Packet requestPacket = new Packet(request);
            Send(requestPacket, null);
        }

        public void AcceptRequest(out Packet request)
        {            
            request = Recieve();
        }

        public void SendReply(Packet reply)
        {
            Send(reply,null);
        }
            
        public Socket Socket
        {
            get
            {
                return m_socket;
            }
        }

        public void Check()
        {
            if (Socket != null && !IsClosed())
            {
                if (Socket.Poll(0, SelectMode.SelectRead) && Socket.Available==0)
                {
                    Close();
                }
            }
        }
   
        virtual public void Close()
        {
            SetCloseFlag();

            if (m_closeHandler != null)
            {
                m_closeHandler(this);
            }
            if( m_socket!=null ) 
            {
                Common.Logger.Log.Info("Close connection - " + RemoteAddress + ".");

                m_socket.Close();
            }
        }

        public bool IsClosed()
        {
            return m_bClosed;
        }
        
        virtual protected void CreateConnection(IPEndPoint remoteEndPoint)
        {
            FillSocket(
                new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp));

            m_socket.Connect(remoteEndPoint);
            FillRemoteLocation(remoteEndPoint);

            Common.Logger.Log.Info("CreateConnection TCP...OK"); 
        }

        virtual public void Send(Packet data, IPEndPoint remoteEndPoint)
        {
            using (NetworkStream stream = new NetworkStream(m_socket))
            {
                data.Serialize(stream);
            }
        }

        virtual public void Recieve(out Packet data, out IPEndPoint remoteEndPoint)
        {
            remoteEndPoint = null;

            SocketStream.StartPacketRecieve();
            data = Packet.Deserialize(SocketStream);
        }

        public void FillSocket(Socket socket)
        {
            m_socket = socket;
            if (m_socket != null)
            {
#if !WindowsCE
                m_socket.ReceiveTimeout = Transport.SendReceiveTimeout;
                m_socket.SendTimeout = Transport.SendReceiveTimeout;
#endif
            }
        }


        private Packet Recieve()
        {
            Packet data;
            IPEndPoint remoteEndPoint;

            Recieve(out data, out remoteEndPoint);
            return data;
        }

        private void FillRemoteLocation(IPEndPoint remoteEndPoint)
        {
            m_remoteLocation = remoteEndPoint.Address.ToString();
            m_remotePort = remoteEndPoint.Port;
        }

        protected void SetCloseFlag()
        {
            m_bClosed = true;
        }

        SocketStream SocketStream
        {
            get
            {
                if (m_stream == null)
                    m_stream = new SocketStream(m_socket, Transport.SendReceiveTimeout);

                return m_stream;
            }
        }
                
        protected Socket m_socket = null;
        protected bool m_bClosed = false;

        SocketStream m_stream;
        string m_remoteLocation = "";
        int m_remotePort = 0;
        ConnectionCloseHandler m_closeHandler;

    }
}
